package com.hwy.chart.component

import android.graphics.Canvas
import android.graphics.RectF
import android.text.TextUtils
import androidx.annotation.MainThread
import com.hwy.chart.BaseChartView
import com.hwy.chart.coordinate.axis.ChartYAxis
import com.hwy.chart.cursor.OnCursorChangeListener
import kotlin.math.abs
import kotlin.math.max

/**
 * =======================================
 * 作者: hewenyu
 * 日期: 2022/5/10 15:30
 * 说明: 图表内容组件,一个图表可以持有多个内容组件(可以理解为多条折线)
 * =======================================
 */
abstract class BaseComponent<T>(protected val chartView: BaseChartView) :
    OnCursorChangeListener where T : Comparable<T>, T : Number {

    /**
     * 图表组件的数据集
     */
    protected val dataList by lazy {
        ArrayList<T?>()
    }

    /**
     * 是否绘制当前组件
     * true:绘制
     * false:不绘制
     */
    var isShow = true
        set(value) {
            if (field == value) {
                return
            }
            field = value
            chartView.requestLayout()
            chartView.invalidate()
        }

    /**
     * 记录当前组件的最大值
     */
    var maxValue: T? = null

    /**
     * 记录当前组件的最小值
     */
    var minValue: T? = null

    /**
     * 绘制组件和光标的交点
     */
    var componentFocus = ComponentFocus()

    /**
     * 光标位置
     */
    var cursorIndex: Int? = null

    /**
     * 与当前component绑定的y轴
     */
    private var yAxis: ChartYAxis<T>? = null

    /**
     * 横轴数据偏移量
     */
    protected var dataOffset = 0

    /**
     * 记录数据快照，防止每次[drawComponent]时，都要重新计算path
     */
    private var snapshot = ""

    /**
     * 判断组件数据是否有效
     */
    fun isDataValid(): Boolean {
        return dataList.isNotEmpty()
    }

    protected fun isDataChange(): Boolean {
        // 快照数据由：y轴数据集 + 组件数据集 + 组件是否显示 组成
        val currSnapShot = "${yAxis?.getSnapshot()}${dataList}${isShow}"
        return if (TextUtils.equals(currSnapShot, snapshot)) {
            false
        } else {
            snapshot = currSnapShot
            true
        }
    }

    /**
     * 全量更新组件的数据集
     */
    @MainThread
    fun updateComponentDataList(list: ArrayList<T?>?) {
        dataList.clear()
        maxValue = null
        minValue = null
        list?.run {
            dataList.addAll(this)
            dataList.forEach {
                updateThreshold(it)
            }
        }
        yAxis?.notifyDataChange()
    }

    /**
     * 增量更新数据
     */
    @MainThread
    fun incrementData(data: T?) {
        dataList.add(data)
        updateThreshold(data)
    }

    /**
     * 更新最大最小值
     */
    protected open fun updateThreshold(data: T?) {
        data?.run {
            // 计算最大值
            if (maxValue == null) {
                maxValue = this
            } else {
                maxValue?.let {
                    // maxValue < data 时 返回值 result 为负数
                    val result = it.compareTo(this)
                    if (result < 0) {
                        // 更新 maxValue
                        maxValue = this
                    }
                }
            }

            // 计算最小值
            if (minValue == null) {
                minValue = this
            } else {
                minValue?.let {
                    // minValue > data 时 返回值 result 为正数
                    val result = it.compareTo(this)
                    if (result > 0) {
                        // 更新 minValue
                        minValue = this
                    }
                }
            }
        }
    }

    override fun onCursorChange(fromIndex: Int?, toIndex: Int?) {
        cursorIndex = toIndex
    }

    /**
     * 计算组件和光标交点的x坐标
     *
     * @param index 当前光标对应的数据集index
     * @param bounds 图表的绘制区域
     * @param list 组件的数据列表 传入 null 表示使用默认的数据列表[dataList]
     */
    protected open fun calculateXCoordinate(
        index: Int,
        bounds: RectF,
        list: ArrayList<T?>? = null
    ): Float {
        // 计算两个数据之间的间距
        val size = list?.size ?: dataList.size
        val dataStep = bounds.width() / (size - 1)
        return index * dataStep + bounds.left
    }

    /**
     * 计算组件和光标交点的y坐标
     *
     * @param index 当前数据对应的[dataList]下标
     * @param data 当前光标对应的数据值
     * @param bounds 图表的绘制区域
     */
    protected open fun calculateYCoordinate(index: Int, data: T?, bounds: RectF): Float? {
        return data?.run {
            this.toFloat().let { value ->
                chartView.coordinate?.yAxis?.let { yAxis ->
                    yAxis.maxValue?.toFloat()?.let { maxValue ->
                        yAxis.minValue?.toFloat()?.let { minValue ->
                            return bounds.bottom - (abs(value - minValue) / abs(minValue - maxValue)) * bounds.height()
                        }
                    }
                }

            }
        }
    }

    /**
     * 将当前组件与坐标轴绑定
     */
    fun bindAxis(yAxis: ChartYAxis<T>?) {
        this.yAxis = yAxis
        yAxis?.bindComponent(this)
    }

    /**
     * 解绑组件与坐标轴
     */
    fun unBindAxis(yAxis: ChartYAxis<T>?) {
        yAxis?.unBindComponent(this)
        this.yAxis = null
    }

    /**
     * 更新坐标轴横轴绘制的点数量
     */
    protected fun updateAxisDataOffset(dataOffset: Int) {
        chartView.coordinate.xAxis.dataOffset =
            max(chartView.coordinate.xAxis.dataOffset, dataOffset)
    }

    // region -------------------------------- 组件绘制相关函数 --------------------------------

    /**
     * 在 [draw] 函数之前执行的函数，可用于预处理绘制
     */
    open fun beforeDraw(canvas: Canvas, bounds: RectF) {
        // 重置坐标轴横轴绘制的点数量
        chartView.coordinate.xAxis.dataOffset = 0
    }

    /**
     * 绘制组件
     */
    open fun draw(canvas: Canvas, bounds: RectF) {
        if (isShow) {
            drawComponent(canvas, bounds)
        }
    }

    /**
     * 在 [draw] 函数之后执行的函数，可用于绘制全局最顶层的内容
     */
    open fun afterDraw(canvas: Canvas, bounds: RectF) {
        // 光标交点默认绘制在最顶层
        drawFocus(canvas, bounds)
    }

    /**
     * 绘制光标交点
     */
    protected open fun drawFocus(canvas: Canvas, bounds: RectF) {
        if (isShow) {
            cursorIndex?.let { index ->
                if (index >= 0 && index < dataList.size) {
                    componentFocus?.run {
                        // 计算交点坐标
                        val xCoordinate = calculateXCoordinate(index, bounds)
                        val yCoordinate = calculateYCoordinate(index, dataList[index], bounds)
                        // 绘制折线和光标的交点
                        draw(canvas, bounds, xCoordinate, yCoordinate)
                    }
                }
            }
        }
    }

    /**
     * 绘制组件内容
     */
    protected abstract fun drawComponent(canvas: Canvas, bounds: RectF)

    // endregion ----------------------------------------------------------------------------

}